/*
 * Created by kopper on 01.02.15 08:50
 * (C) Copyright 2015 kopperek@gmail.com
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl-2.1.html
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 */

package app.android.kopper.gpxdownloader.reader;

import android.hardware.usb.UsbDeviceConnection;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

import app.android.kopper.gpxdownloader.IDataReader;
import app.android.kopper.gpxdownloader.IUtilProvider;
import app.android.kopper.gpxdownloader.ProgressParam;
import app.android.kopper.gpxdownloader.R;
import app.android.kopper.gpxdownloader.UsbDevice;
import app.android.kopper.gpxdownloader.parser.GR245Parser;

public class GR245Reader implements IDataReader {


    private static final int SILABSER_IFC_ENABLE_REQUEST_CODE = 0x00;
    private static final int SILABSER_SET_BAUDDIV_REQUEST_CODE = 0x01;
    private static final int SILABSER_SET_MHS_REQUEST_CODE = 0x07;

    private static final int UART_ENABLE = 0x0001;
    private static final int UART_DISABLE = 0x0000;

    private static final int MCR_ALL = 0x0003;

    private static final int CONTROL_WRITE_DTR = 0x0100;
    private static final int CONTROL_WRITE_RTS = 0x0200;

    private static final int BAUD_RATE_GEN_FREQ = 0x384000;
    private static final int DEFAULT_BAUD_RATE = 38400;

    private static final int REQTYPE_HOST_TO_DEVICE = 0x41;

    private static final int USB_WRITE_TIMEOUT_MILLIS = 5000;

    private static final byte[] WHO_ARE_YOU= new byte[] {'$','P','M','T','K','6','0','5','*','3','1',0x0d,0x0a};
    private static final byte[] HELLO_1= new byte[] {'$','P','H','L','X','8','1','0','*','3','5',0x0d,0x0a};
    private static final byte[] HELLO_2= new byte[] {'$','P','H','L','X','8','2','6','*','3','0',0x0d,0x0a};

    private static final byte[] GET_LOG_SIZE= new byte[] {'$','P','M','T','K','1','8','2',',','2',',','8','*','3','3',0x0d,0x0a};
    private static final byte[] BY= new byte[] {'$','P','H','L','X','8','2','7','*','3','1',0x0d,0x0a};

    @Override
    public String performReading(final UsbDevice usbDevice,final UsbDeviceConnection connection,IUtilProvider utils) throws IOException {

        setConfigSingle(connection,SILABSER_IFC_ENABLE_REQUEST_CODE,UART_ENABLE);
        setConfigSingle(connection,SILABSER_SET_MHS_REQUEST_CODE,MCR_ALL|CONTROL_WRITE_DTR|CONTROL_WRITE_RTS);
        setConfigSingle(connection,SILABSER_SET_BAUDDIV_REQUEST_CODE,BAUD_RATE_GEN_FREQ/DEFAULT_BAUD_RATE);

        utils.publishProgress(new ProgressParam(utils.getString(R.string.progress_checking_device),"",0,0));
        sendAndReceive(usbDevice,connection,WHO_ARE_YOU,utils);
        sendAndReceive(usbDevice,connection,HELLO_1,utils);
        sendAndReceive(usbDevice,connection,HELLO_2,utils);

        utils.publishProgress(new ProgressParam(utils.getString(R.string.progress_checking_size),"",0,0));
        String logSizeString=sendAndReceive(usbDevice,connection,GET_LOG_SIZE,utils);
        //size: $PMTK182,3,8,00199A10*6F

        File rawFile=utils.createFile("import.raw");
        FileOutputStream fos=new FileOutputStream(rawFile);

        Integer size = Integer.valueOf(extractResultValue(logSizeString),16);
        utils.publishProgress(new ProgressParam(utils.getString(R.string.progress_reading),"0/"+size,0,0));
        //i lecimy po wierszach
        int index=0;
        int blockSize=0x400;
        for(;;) {
            if(index>size)
                break;
            String command = String.format("PMTK182,7,%08x,%08x",index,blockSize);
            command="$"+command+"*"+calculateHash(command)+"\r\n";
            String dataLine=extractResultValue(sendAndReceive(usbDevice,connection,command.getBytes(),utils));
            int dataLength=dataLine.length()/2;
            byte[] rawBytes=new byte[dataLength];
            for(int a=0;a<dataLength;a++) {
                rawBytes[a]=Integer.valueOf(dataLine.substring(a*2,a*2+2),16).byteValue();
            }
            fos.write(rawBytes);
            index+=blockSize;
            utils.publishProgress(new ProgressParam(utils.getString(R.string.progress_reading),index+"/"+size,index,size.intValue()));
        }
        fos.close();
        utils.publishProgress(new ProgressParam(utils.getString(R.string.progress_disconnecting),"",0,0));
        sendAndReceive(usbDevice,connection,BY,utils);
        utils.publishProgress(new ProgressParam(utils.getString(R.string.progress_parsing),"",0,0));
        new GR245Parser(rawFile, index).parse(utils);
        utils.publishProgress(new ProgressParam(utils.getString(R.string.progress_done),"",0,0));
        return (null);
    }

    private String extractResultValue(String logSizeString) {
        return logSizeString.substring(logSizeString.lastIndexOf(',')+1,logSizeString.lastIndexOf('*'));
    }

    private static String calculateHash(String command) {
        byte sum=0;
        byte[] bytes = command.getBytes();
        for(int a=0;a<bytes.length;a++)
            sum^=bytes[a];
        return String.format("%02X",sum);
    }


    private String sendAndReceive(UsbDevice usbDevice,UsbDeviceConnection connection,byte[] bytes,IUtilProvider utils) throws IOException {
        usbDevice.send(connection,bytes);
        String result=receive(usbDevice,connection,utils);
        return(result);
    }

    private String receive(UsbDevice usbDevice,UsbDeviceConnection connection,IUtilProvider utils) throws IOException {
        return receiveAnswer(usbDevice,connection,new HashSet<String>(
                Arrays.asList(
                        "^"+Pattern.quote("$")+"G.*$",
                        "^[^"+Pattern.quote("$")+"].*$",
                        "^"+Pattern.quote("$")+"PMTK001,.*$"
                )),utils);
    }

    private String receiveAnswer(UsbDevice usbDevice,UsbDeviceConnection connection,Set<String> omitList,IUtilProvider utils) throws IOException {
        ByteArrayOutputStream baos=new ByteArrayOutputStream();
        byte[] buffer=new byte[4096];
        for(;;) {
            int received=usbDevice.receive(connection,buffer);
            if(received<0)
                throw new IOException(utils.getString(R.string.error_communication));
            baos.write(buffer,0,received);
            byte[] receivedSoFar=baos.toByteArray();
//            writer.println("ReceivedSoFar: "+new String(receivedSoFar,0,receivedSoFar.length));
            int index=indexOf(receivedSoFar,(byte)0x0d);
            if(index!=-1) {
                String value=new String(receivedSoFar,0,index);
                //is it forbidden
                loop:
                for(;;) {
                    for(String omit:omitList) {
                        if(value.matches(omit))
                            break loop;
                    }
                    return value;
                }
                baos=new ByteArrayOutputStream();
                int len=receivedSoFar.length-index-2;
                if(len>0)
                    baos.write(receivedSoFar,index+2,len);
            }
        }
    }

    private int indexOf(byte[] array,byte b) {
        for(int a=0;a<array.length;a++)
            if(array[a]==b)
                return(a);
        return(-1);
    }

    private int setConfigSingle(UsbDeviceConnection connection,int request,int value) {
        return connection.controlTransfer(REQTYPE_HOST_TO_DEVICE, request, value, 0, null, 0, USB_WRITE_TIMEOUT_MILLIS);
    }
}
